package cn.yx.common.core.util.reflect;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.yx.common.core.constant.SeparatorConstant;
import cn.yx.common.core.constant.message.reflect.ClassUtilMessageKey;
import cn.yx.common.core.enums.CharsetEnum;
import cn.yx.common.core.exception.CommonException;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * <p>类相关工具类</p>
 *
 * @author Wgssmart
 */
@Slf4j
public final class ClassUtil {

    public static final String CLASS_PATH_PREFIX = File.separator + "classes" + File.separator;

    public static final String CLASS_SUFFIX = ".class";

    public static final String PROTOCOL_FILE = "file";

    public static final String PROTOCOL_JAR = "jar";

    private ClassUtil() {
    }

    /**
     * 判断是否为数字类型
     *
     * @param clazz
     * @return
     */
    public static boolean isNumberType(Class<?> clazz) {
        if (Objects.isNull(clazz)) {
            return false;
        }
        return clazz == Number.class || clazz.getSuperclass() == Number.class;
    }

    /**
     * 获取指定包中的所有带有指定注解的类名称
     *
     * @param packageName 指定包名
     * @param recursive   是否迭代查询包
     * @param annotation  获取带有此Annotation的类信息
     * @return
     */
    public static List<String> getClassNames(String packageName, Boolean recursive, Class<? extends Annotation> annotation) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(SeparatorConstant.DOT, SeparatorConstant.SLASH);
        List<String> classNames = new ArrayList<>();
        try {
            Enumeration<URL> urlEnumeration = classLoader.getResources(packagePath);
            while (urlEnumeration.hasMoreElements()) {
                URL url = urlEnumeration.nextElement();
                String protocol = url.getProtocol();
                if (PROTOCOL_FILE.equals(protocol)) {
                    classNames.addAll(getClassNames(new File(url.getPath()), recursive, annotation));
                } else if (PROTOCOL_JAR.equals(protocol)) {
                    classNames.addAll(getClassNames(((JarURLConnection) url.openConnection()).getJarFile(), annotation));
                }
            }
            return classNames;
        } catch (IOException e) {
            log.error("get class names error: {}, package name: {}, recursive: {}, annotation: {}",
                    e.getMessage(), packageName, recursive, annotation);
            throw new CommonException(ClassUtilMessageKey.GET_CLASS_NAMES_ERROR, e);
        }
    }

    /**
     * 获取指定包中的所有类名称
     *
     * @param packageName 指定包名
     * @param recursive   是否迭代查询包
     * @return
     */
    public static List<String> getClassNames(String packageName, Boolean recursive) {
        return getClassNames(packageName, recursive, null);
    }

    /**
     * 获取指定包中的所有带有指定注解的类
     *
     * @param packageName 指定包名
     * @param recursive   是否迭代查询包
     * @param annotation  如果指定了Annotation，则只会去获取带有此Annotation的类信息，如果未指定，为null，则获取所有的类信息
     * @return
     */
    public static List<Class<?>> getClasses(String packageName, Boolean recursive, Class<? extends Annotation> annotation) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(SeparatorConstant.DOT, SeparatorConstant.SLASH);
        List<Class<?>> classes = new ArrayList<>();
        try {
            Enumeration<URL> urlEnumeration = classLoader.getResources(packagePath);
            while (urlEnumeration.hasMoreElements()) {
                URL url = urlEnumeration.nextElement();
                String protocol = url.getProtocol();
                if (PROTOCOL_FILE.equals(protocol)) {
                    classes.addAll(getClasses(new File(URLDecoder.decode(url.getPath(), CharsetEnum.UTF8.getValue())), recursive, annotation));
                } else if (PROTOCOL_JAR.equals(protocol)) {
                    classes.addAll(getClasses(((JarURLConnection) url.openConnection()).getJarFile(), annotation));
                }
            }
            return classes;
        } catch (IOException e) {
            log.error("get classes error: {}, package name: {}, recursive: {}, annotation: {}",
                    e.getMessage(), packageName, recursive, annotation);
            throw new CommonException(ClassUtilMessageKey.GET_CLASSES_ERROR, e);
        }
    }

    /**
     * 获取指定包中的所有类
     *
     * @param packageName 指定包名
     * @param recursive   是否迭代查询包
     * @return
     */
    public static List<Class<?>> getClasses(String packageName, Boolean recursive) {
        return getClasses(packageName, recursive, null);
    }

    /**
     * 以文件的方式从目录中获取类名称
     *
     * @param dir
     * @param recursive
     * @param annotation
     * @return
     */
    public static List<String> getClassNames(File dir, Boolean recursive, Class<? extends Annotation> annotation) {
        List<String> classNames = new ArrayList<>();
        File[] files = dir.listFiles();
        if (ArrayUtil.isNotEmpty(files)) {
            for (File file : files) {
                if (!file.isDirectory()) {
                    String className = getClassNameFromFile(file, annotation);
                    if (StrUtil.isNotEmpty(className)) {
                        classNames.add(className);
                    }
                } else {
                    if (recursive) {
                        classNames.addAll(getClassNames(file, true, annotation));
                    }
                }
            }
        }
        return classNames;
    }

    private static String getClassNameFromFile(File file, Class<? extends Annotation> annotation) {
        String classPath = file.getPath();
        // 过滤掉所有内部类
        if (classPath.endsWith(CLASS_SUFFIX) && !isInnerClass(classPath)) {
            String className = classPath.substring(classPath.indexOf(CLASS_PATH_PREFIX) + CLASS_PATH_PREFIX.length())
                    .replace(CLASS_SUFFIX, "")
                    .replace(File.separator, SeparatorConstant.DOT);
            return Objects.nonNull(annotation) && isNotAnnotationPresent(className, annotation) ? null : className;
        }
        return null;
    }

    /**
     * 以文件的方式从目录中获取类名称
     *
     * @param dir
     * @param recursive
     * @return
     */
    public static List<String> getClassNames(File dir, Boolean recursive) {
        return getClassNames(dir, recursive, null);
    }

    /**
     * 以文件的方式获取类
     *
     * @param dir
     * @param recursive
     * @param annotation
     * @return
     */
    public static List<Class<?>> getClasses(File dir, Boolean recursive, Class<? extends Annotation> annotation) {
        List<Class<?>> classes = new ArrayList<>();
        File[] files = dir.listFiles();
        if (ArrayUtil.isNotEmpty(files)) {
            for (File file : files) {
                if (!file.isDirectory()) {
                    Class<?> clazz = getClassFromFile(file, annotation);
                    if (Objects.nonNull(clazz)) {
                        classes.add(clazz);
                    }
                } else {
                    if (recursive) {
                        classes.addAll(getClasses(file, true, annotation));
                    }
                }
            }
        }
        return classes;
    }

    private static Class<?> getClassFromFile(File file, Class<? extends Annotation> annotation) {
        String classPath = file.getPath();
        // 过滤掉所有内部类
        if (classPath.endsWith(CLASS_SUFFIX) && !isInnerClass(classPath)) {
            String className = classPath.substring(classPath.indexOf(CLASS_PATH_PREFIX) + CLASS_PATH_PREFIX.length())
                    .replace(CLASS_SUFFIX, "")
                    .replace(File.separator, ".");
            try {
                Class<?> clazz = Class.forName(className);
                return Objects.nonNull(annotation) && isNotAnnotationPresent(clazz, annotation) ? null : clazz;
            } catch (ClassNotFoundException e) {
                log.error("get classes error: {}, file name: {}, annotation: {}",
                        e.getMessage(), file.getName(), annotation);
                throw new CommonException(ClassUtilMessageKey.GET_CLASSES_ERROR, e);
            }
        }
        return null;
    }

    /**
     * 以文件的方式获取类
     *
     * @param file
     * @param recursive
     * @return
     */
    public static List<Class<?>> getClasses(File file, Boolean recursive) {
        return getClasses(file, recursive, null);
    }

    /**
     * 通过JarFile获取类名称
     *
     * @param jarFile
     * @param annotation
     * @return
     */
    public static List<String> getClassNames(JarFile jarFile, Class<? extends Annotation> annotation) {
        List<String> classNames = new ArrayList<>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String name = jarEntry.getName();
            // 过滤掉所有内部类
            if (name.endsWith(CLASS_SUFFIX) && !isInnerClass(name)) {
                String className = name.replace(CLASS_SUFFIX, "")
                        .replace(SeparatorConstant.SLASH, SeparatorConstant.DOT);
                if (Objects.nonNull(annotation) && isNotAnnotationPresent(className, annotation)) {
                    continue;
                }
                classNames.add(className);
            }
        }
        return classNames;
    }

    /**
     * 通过JarFile获取类名称
     *
     * @param jarFile
     * @return
     */
    public static List<String> getClassNames(JarFile jarFile) {
        return getClassNames(jarFile, null);
    }

    /**
     * 通过JarFile获取类名称，jar为外部文件，没有在当前项目中加载到JVM。
     * 由于是外部JAR文件，所以class类需要使用URLClassLoader的loadClass方法加载。
     *
     * @param jarFile
     * @param annotation
     * @return
     */
    public static List<String> getClassNamesFromOutJar(File jarFile, Class<? extends Annotation> annotation) {
        try (URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURI().toURL()});
             JarFile theJarFile = new JarFile(jarFile)) {
            List<String> classNames = new ArrayList<>();
            Enumeration<JarEntry> entries = theJarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                // 过滤掉所有内部类
                if (name.endsWith(CLASS_SUFFIX) && !isInnerClass(name)) {
                    String className = name.replace(CLASS_SUFFIX, "")
                            .replace(SeparatorConstant.SLASH, SeparatorConstant.DOT);
                    Class<?> clazz = urlClassLoader.loadClass(className);
                    if (Objects.nonNull(annotation) && isNotAnnotationPresent(clazz, annotation)) {
                        continue;
                    }
                    classNames.add(className);
                }
            }
            return classNames;
        } catch (ClassNotFoundException | IOException e) {
            log.error("get out class names error: {}, jar file name: {}, annotation: {}",
                    e.getMessage(), jarFile.getName(), annotation);
            throw new CommonException(ClassUtilMessageKey.GET_OUT_CLASS_NAMES_ERROR, e);
        }
    }

    /**
     * 通过JarFile获取类名称，jar为外部文件，没有在当前项目中加载到JVM
     *
     * @param jarFile
     * @return
     */
    public static List<String> getClassNamesFromOutJar(File jarFile) {
        return getClassNamesFromOutJar(jarFile, null);
    }

    /**
     * 通过JarFile获取类
     *
     * @param jarFile
     * @param annotation
     * @return
     */
    public static List<Class<?>> getClasses(JarFile jarFile, Class<? extends Annotation> annotation) {
        List<Class<?>> classes = new ArrayList<>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String name = jarEntry.getName();
            // 过滤掉所有内部类
            if (name.endsWith(CLASS_SUFFIX) && !isInnerClass(name)) {
                String className = name.replace(CLASS_SUFFIX, "")
                        .replace(SeparatorConstant.SLASH, SeparatorConstant.DOT);
                try {
                    Class<?> clazz = Class.forName(className);
                    if (Objects.nonNull(annotation) && isNotAnnotationPresent(clazz, annotation)) {
                        continue;
                    }
                    classes.add(clazz);
                } catch (ClassNotFoundException e) {
                    log.error("get classes error: {}, jar file name: {}, annotation: {}, class name: {}",
                            e.getMessage(), jarFile.getName(), annotation, className);
                    throw new CommonException(ClassUtilMessageKey.GET_CLASSES_ERROR, e);
                }
            }
        }
        return classes;
    }

    /**
     * 通过JarFile获取类
     *
     * @param jarFile
     * @return
     */
    public static List<Class<?>> getClasses(JarFile jarFile) {
        return getClasses(jarFile, null);
    }

    /**
     * 通过JarFile获取类，jar为外部文件，没有在当前项目中加载到JVM。
     * 由于是外部JAR文件，所以class类需要使用URLClassLoader的loadClass方法加载。
     *
     * @param jarFile
     * @param annotation
     * @return
     */
    public static List<Class<?>> getClassesFromOutJar(File jarFile, Class<? extends Annotation> annotation) {
        try (URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURI().toURL()});
             JarFile theJarFile = new JarFile(jarFile)) {
            List<Class<?>> classes = new ArrayList<>();
            Enumeration<JarEntry> entries = theJarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                // 过滤掉所有内部类
                if (name.endsWith(CLASS_SUFFIX) && !isInnerClass(name)) {
                    String className = name.replace(CLASS_SUFFIX, "")
                            .replace(SeparatorConstant.SLASH, SeparatorConstant.DOT);
                    Class<?> clazz = urlClassLoader.loadClass(className);
                    if (Objects.nonNull(annotation) && isNotAnnotationPresent(clazz, annotation)) {
                        continue;
                    }
                    classes.add(clazz);
                }
            }
            return classes;
        } catch (IOException | ClassNotFoundException e) {
            log.error("get classes error: {}, jar file name: {}, annotation: {}",
                    e.getMessage(), jarFile.getName(), annotation);
            throw new CommonException(ClassUtilMessageKey.GET_OUT_CLASSES_ERROR, e);
        }
    }

    /**
     * 通过JarFile获取类，jar为外部文件，没有在当前项目中加载到JVM
     *
     * @param jarFile
     * @return
     */
    public static List<Class<?>> getClassesFromOutJar(File jarFile) {
        return getClassesFromOutJar(jarFile, null);
    }

    /**
     * 判断指定类名的类是否为内部类
     *
     * @param className
     * @return
     */
    public static boolean isInnerClass(String className) {
        return className.contains("$");
    }

    /**
     * 判断指定的类是否为内部类
     *
     * @param clazz
     * @return
     */
    public static boolean isInnerClass(Class<?> clazz) {
        return isInnerClass(clazz.getName());
    }

    /**
     * 判断指定类名的类是否带有指定的注解
     *
     * @param className
     * @param annotation
     * @return
     */
    public static boolean isAnnotationPresent(String className, Class<? extends Annotation> annotation) {
        try {
            return isAnnotationPresent(Class.forName(className), annotation);
        } catch (ClassNotFoundException | NoClassDefFoundError | ExceptionInInitializerError e) {
            log.error("is annotation present error: {}, class name: {}, annotation: {}, class name: {}",
                    e.getMessage(), className, annotation, className);
            throw new CommonException(ClassUtilMessageKey.IS_CLASS_WITH_ANNOTATION_ERROR, e);
        }
    }

    /**
     * 判断指定的类是否带有指定的注解
     *
     * @param clazz
     * @param annotation
     * @return
     */
    public static boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation) {
        return clazz.isAnnotationPresent(annotation);
    }

    /**
     * 判断指定类名的类是否不带有指定的注解
     *
     * @param className
     * @param annotation
     * @return
     */
    public static boolean isNotAnnotationPresent(String className, Class<? extends Annotation> annotation) {
        try {
            return isNotAnnotationPresent(Class.forName(className), annotation);
        } catch (ClassNotFoundException | NoClassDefFoundError | ExceptionInInitializerError e) {
            log.error("is not annotation present error: {}, class name: {}, annotation: {}, class name: {}",
                    e.getMessage(), className, annotation, className);
            throw new CommonException(ClassUtilMessageKey.IS_CLASS_WITH_ANNOTATION_ERROR, e);
        }
    }

    /**
     * 判断指定的类是否不带有指定的注解
     *
     * @param clazz
     * @param annotation
     * @return
     */
    public static boolean isNotAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation) {
        return !clazz.isAnnotationPresent(annotation);
    }

    /**
     * 创建新实例
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T newInstance(Class<T> clazz) {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                 NoSuchMethodException e) {
            log.error("new instance error: {}", e.getMessage());
            throw new CommonException(ClassUtilMessageKey.NEW_INSTANCE_ERROR, e);
        }
    }

}